
import {ø} from "../util/objects.js";
import * as utc from "../util/utc.js";
import {convertToNum} from "../util/values.js";
import {pad} from "../util/strings.js";
import {getQueryObject} from "../util/query.js";

function fragmentToState(param, level) {
    const mode = param === "wind" ? "air" : param;
    switch (level) {
        case "currents": return {mode, animation_type: "currents", z_level: "surface"};
        case "level": return {mode, animation_type: "wind", z_level: "surface"};
        case "waves": return {mode, animation_type: "primary_waves", z_level: "surface"};
    }
    return {mode, animation_type: "wind", z_level: level};
}

function stateToFragment(mode, animation_type, z_level) {
    const param = mode === "air" ? "wind" : mode;
    switch (animation_type) {
        case "currents": return [param, "surface", "currents"];
        case "primary_waves": return [param, "primary", "waves"];
    }
    switch (z_level) {
        case "surface": return [param, "surface", "level"];
    }
    return [param, "isobaric", z_level];
}

export function fragmentToOrientation(s) {
    const parts = s?.split(",") ?? [];

    const λ = -convertToNum(parts[0]);
    const φ = -convertToNum(parts[1]);
    const rotate = (λ === λ && φ === φ) ? [λ, φ] : undefined;

    const k = convertToNum(parts[2]);
    const scale = k === k ? k : undefined;

    return ø({rotate, scale});
}

export function orientationToFragment(o) {
    const l = -(o?.rotate?.[0]);
    const p = -(o?.rotate?.[1]);
    const k = +(o?.scale);
    const coord = `${l.toFixed(2)},${p.toFixed(2)}`;
    const scale = `${Math.round(k)}`;

    if (l !== l || p !== p) {
        if (k !== k) {
            return undefined;
        }
        return `,,${scale}`;
    } else if (k !== k) {
        return coord;
    }
    return `${coord},${scale}`;
}

function interpretTimeCursor(tokens) {
    if (tokens[1] === "current") {
        return "current";
    }
    const year = convertToNum(tokens[2]);
    const month = convertToNum(tokens[3]);
    const day = convertToNum(tokens[4]);
    const hhmm = pad(tokens[5], 4);
    const hour = convertToNum(hhmm.substr(0, 2));
    const minute = convertToNum(hhmm.substr(2));
    return utc.normalize({year, month, day, hour, minute});
}

/**
 * @param {string} hash
 * @param {Set} allowedProjections
 * @param {Set} allowedOverlays
 * @param {Function} initialState
 * @returns {Object} the parsed attributes
 */
export function fromQueryFragment(hash, allowedProjections, allowedOverlays, initialState) {
    const result = initialState();

    //              cursor    YYYY      MM         DD       hhmmZ        param surface level  loc|overlay|grid|...
    //                1        2        3          4          5            6      7      8    9
    const tokens = /^#(current|(\d{4})\/(\d{1,2})\/(\d{1,2})\/(\d{3,4})Z)\/(\w+)\/(\w+)\/(\w+)([/].+)?/.exec(hash);
    if (!tokens) {
        return result;
    }
    const {
        mode,
        projection_type,
        orientation,
        time_cursor,
        z_level,
        animation_type,
        overlay_type,
        spotlight_coords,
        show_grid_points,
        animation_enabled,
        // argoFloat,
    } = attr;
    
    result.time_cursor = interpretTimeCursor(tokens);
    Object.assign(result, fragmentToState(tokens[6], tokens[8]));
    result.overlay_type = result.animation_type;  // by default, use overlay that matches the animation

    let option;
    (tokens[9] ?? "").split("/").forEach(segment => {
        if ((option = /^(\w+)(=([\d\-.,]*))?$/.exec(segment))) {
            if (allowedProjections.has(option[1])) {
                result.projection_type = option[1];
                result.orientation = fragmentToOrientation(option[3]);
            } else if (option[1] === "loc") {
                const parts = typeof option[3] === "string" ? option[3].split(",") : [];
                const λ = convertToNum(parts[0])
                const φ = convertToNum(parts[1]);
                if (λ === λ && φ === φ) {
                    result.spotlight_coords = [λ, φ];
                }
            }
        } else if ((option = /^overlay=([\w.]+)$/.exec(segment))) {
            const overlay_type = option[1] === "off" ? "none" : option[1];
            if (allowedOverlays.has(overlay_type)) {
                result.overlay_type = overlay_type;
            }
        } else if ((option = /^grid=(\w+)$/.exec(segment))) {
            if (option[1] === "on") {
                result.show_grid_points = true;
            }
        } else if ((option = /^anim=(\w+)$/.exec(segment))) {
            if (option[1] === "off") {
                result.animation_enabled = false;
            }
        }/* else if ((option = /^argo=(\w+)$/.exec(segment))) {
            switch (option[1]) {
                case "planned":
                case "recent":
                case "operational":
                case "dead":
                    result.argoFloat = option[1];
            }
        }*/
    });
    return result;
}

export function toHashFragment(attr) {
    const {
        mode,
        projection_type,
        orientation,
        time_cursor,
        z_level,
        animation_type,
        overlay_type,
        spotlight_coords,
        show_grid_points,
        animation_enabled,
        // argoFloat,
    } = attr;

    const dir = time_cursor === "current" ? "current" : utc.print(time_cursor, "{YYYY}/{MM}/{DD}/{hh}{mm}Z");
    const [param, surface, level] = stateToFragment(mode, animation_type, z_level);
    const anim = animation_enabled ? "" : "anim=off";
    const ot = !overlay_type || overlay_type === animation_type ? "" : `overlay=${overlay_type}`;
    const grid = show_grid_points ? "grid=on" : "";
    // const argo = argoFloat ? "argo=" + argoFloat : "";
    const proj = [projection_type, orientationToFragment(orientation)].filter(x => x).join("=");
    const loc = spotlight_coords ? "loc=" + spotlight_coords.map(e => e.toFixed(3)).join(",") : "";
    return "#" + [dir, param, surface, level, anim, ot, grid, /*argo,*/ proj, loc].filter(x => x).join("/");
}
